package com.sxf.mybatis.enhance;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;

public class IOUtils {
	private static final int DEFAULT_BUFFER_SIZE = 4096;
	private static final File[] EMPTY = new File[0];

	/**
	 * 关闭指定的对象，不会抛出异常
	 * 
	 * @param input
	 *            需要关闭的资源
	 */
	public static void closeQuietly(Closeable input) {
		if (input != null) {
			try {
				input.close();
			} catch (IOException e) {
				LogUtil.exception(e);
			}
		}
	}

	/**
	 * 将URL转化为文件
	 * 
	 * @param url
	 *            要转换的URL，必须是file://协议，否则抛出异常。
	 */
	public static File urlToFile(URL url) {
		if (url == null)
			return null;
		try {
			URLFile file = new URLFile(url);
			if (file.isLocalFile())
				return file.getLocalFile();
			return file;
		} catch (RuntimeException e) {
			LogUtil.error(url.toString() + " is not a valid file:"
					+ e.getMessage());
			return null;
		}
	}

	/**
	 * 将多个URL转换为多个文件对象
	 * 
	 * @param url
	 * @return
	 */
	public static File[] urlToFile(URL[] url) {
		File[] result = new File[url.length];
		for (int i = 0; i < url.length; i++) {
			result[i] = urlToFile(url[i]);
		}
		return result;
	}

	/**
	 * 将指定的流保存为临时文件
	 * 
	 * @param is
	 * @return
	 * @throws IOException
	 */
	public static File saveAsTempFile(InputStream is) throws IOException {
		File f = File.createTempFile("~tmp", ".io");
		saveAsFile(f, is);
		return f;
	}

	/**
	 * 将输入流保存为文件
	 * 
	 * @param is
	 * @param file
	 * @throws IOException
	 */
	public static void saveAsFile(File file, InputStream... iss)
			throws IOException {
		ensureParentFolder(file);
		BufferedOutputStream os = new BufferedOutputStream(
				new FileOutputStream(file));
		try {
			for (InputStream is : iss) {
				copy(is, os, false);
			}
		} finally {
			if (os != null) {
				os.flush();
				os.close();
			}
		}
	}

	/**
	 * 检查/创建文件在所的文件夹
	 * 
	 * @param file
	 */
	public static void ensureParentFolder(File file) {
		File f = file.getParentFile();
		if (f != null && !f.exists()) {
			f.mkdirs();
		} else if (f != null && f.isFile()) {
			throw new RuntimeException(f.getAbsolutePath()
					+ " is a exist file, can't create directory.");
		}
	}

	/*
	 * Copies the contents of the given {@link InputStream} to the given {@link
	 * OutputStream}.
	 * 
	 * @param pIn The input stream, which is being read. It is guaranteed, that
	 * {@link InputStream#close()} is called on the stream.
	 * 关于InputStram在何时关闭的问题，我一直认为应当是成对操作的（即在哪个方法中生成Stream，就要在使用完后关闭），
	 * 因此不打算在这里使用close方法。 但是后来我又考虑到，InputStream在使用完后，其内部标记已经发生了变化，无法再次使用。
	 * (reset方法的效果和实现有关，并不能保证回复到Stream使用前的状态。)
	 * 因此考虑这里统一关闭以防止疏漏，外面再关一次也不会有问题(作为好习惯，还是应该成对打开和关闭)。
	 * 
	 * @param pOut 输出流，可以为null,此时输入流中的相应数据将丢弃
	 * 
	 * @param pClose True guarantees, that {@link OutputStream#close()} is
	 * called on the stream. False indicates, that only {@link
	 * OutputStream#flush()} should be called finally.
	 * 
	 * @param pBuffer Temporary buffer, which is to be used for copying data.
	 * 
	 * @return Number of bytes, which have been copied.
	 * 
	 * @throws IOException An I/O error occurred.
	 */
	private static long copy(InputStream in, OutputStream out, boolean inClose,
			boolean outClose, byte[] pBuffer) throws IOException {
		if (in == null)
			throw new NullPointerException();
		long total = 0;
		try {
			int res;
			while ((res = in.read(pBuffer)) != -1) {
				if (out != null) {
					out.write(pBuffer, 0, res);
				}
				total += res;
			}
			if (out != null)
				out.flush();
		} finally {
			if (outClose)
				closeQuietly(out);
			if (inClose)
				closeQuietly(in);
		}
		return total;
	}

	/*
	 * 同上、READER和Writer之间的拷贝
	 */
	private static long copy(Reader in, Writer out, boolean inClose,
			boolean outClose, char[] pBuffer) throws IOException {
		if (in == null)
			throw new NullPointerException();
		long total = 0;
		try {
			int res;
			while ((res = in.read(pBuffer)) != -1) {
				if (out != null) {
					out.write(pBuffer, 0, res);
				}
				total += res;
			}
			if (out != null)
				out.flush();
		} finally {
			if (outClose && out != null)
				closeQuietly(out);
			if (inClose)
				closeQuietly(in);
		}
		return total;
	}

	/**
	 * 流之间拷贝
	 * 
	 * @param in
	 *            输入
	 * @param out
	 *            输出
	 * @param inClose
	 *            关闭输入流？
	 * @param outClose
	 *            关闭输出流?
	 * @return
	 * @throws IOException
	 */
	public static long copy(InputStream in, OutputStream out, boolean inClose,
			boolean outClose) throws IOException {
		return copy(in, out, inClose, outClose, new byte[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * 流之间拷贝
	 * 
	 * @param in
	 *            输入
	 * @param out
	 *            输出
	 * @param inClose
	 *            关闭输入流
	 * @param outClose
	 *            关闭输出流
	 * @return
	 * @throws IOException
	 */
	public static long copy(Reader in, Writer out, boolean inClose,
			boolean outClose) throws IOException {
		return copy(in, out, inClose, outClose, new char[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * 流之间拷贝
	 * 
	 * @param in
	 *            输入
	 * @param out
	 *            输出
	 * @param pClose
	 *            关闭输出流?
	 * @return 拷贝长度
	 * @throws IOException
	 */
	public static long copy(Reader in, Writer out, boolean pClose)
			throws IOException {
		return copy(in, out, true, pClose, new char[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * 流之间拷贝
	 * 
	 * @param in
	 *            输入
	 * @param out
	 *            输出
	 * @param closeOutStream
	 *            关闭输出流? (输入流默认关闭)
	 * @return
	 * @throws IOException
	 */
	public static long copy(InputStream in, OutputStream out,
			boolean closeOutStream) throws IOException {
		return copy(in, out, true, closeOutStream,
				new byte[DEFAULT_BUFFER_SIZE]);
	}

	/**
	 * 将制定的URL中的数据读出成byte[]
	 * 
	 * @param url
	 *            资源目标位置
	 * @return 字节数组
	 * @throws IOException
	 *             IO操作异常
	 */
	public static byte[] toByteArray(URL url) throws IOException {
		return toByteArray(url.openStream());
	}

	/**
	 * 读取文件到内存(不可用于大文件)
	 * 
	 * @param file
	 *            本地文件
	 * @return 字节数组
	 * @throws IOException
	 *             IO操作异常
	 */
	public static byte[] toByteArray(File file) throws IOException {
		InputStream in = (file instanceof URLFile) ? ((URLFile) file)
				.getInputStream() : new FileInputStream(file);
		try {
			byte[] result = toByteArray(in, (int) file.length());
			return result;
		} finally {
			in.close();
		}
	}

	/**
	 * 读取文件到内存(不可用于大文件)
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 * @deprecated use {@linkp #toByteArray(File)}
	 */
	public static byte[] asByteArray(File file) throws IOException {
		return toByteArray(file);
	}

	/**
	 * 读取流数据到内存。注意这个方法会将数据流全部读入到内存中，因此不适用于很大的数据对象
	 * 
	 * @param in
	 * @return
	 * @throws IOException
	 */
	public static byte[] toByteArray(InputStream in) throws IOException {
		try {
			byte[] msg = toByteArray(in, -1);
			return msg;
		} finally {
			in.close();
		}
	}

	/**
	 * 从流中读取指定的字节，第三个版本，性能再度提升 参考数据，从120M文件中读取前60M，此方法耗时125ms,v2耗时156ms
	 * 
	 * @param in
	 * @param length
	 *            要读取的字节数，-1表示不限制。（注意实际处理中-1的情况下最多读取2G数据，超过2G不会读取）
	 * @return
	 * @throws IOException
	 */
	public static byte[] toByteArray(InputStream in, int length)
			throws IOException {
		ByteArrayOutputStream out;
		if (length > 0) {
			out = new ByteArrayOutputStream(length);
		} else {
			out = new ByteArrayOutputStream(1024);
		}
		int buf = DEFAULT_BUFFER_SIZE;
		byte[] pBuffer = new byte[buf];
		int left = (length > 0) ? length : Integer.MAX_VALUE;// 剩余字节数
		while (left >= buf) {
			int n = in.read(pBuffer);
			if (n == -1) {
				left = 0;
				break;
			}
			left -= n;
			out.write(pBuffer, 0, n);
		}
		while (left > 0) {
			int n = in.read(pBuffer, 0, left);
			if (n == -1) {
				break;
			}
			left -= n;
			out.write(pBuffer, 0, n);
		}
		out.close();// ByteArrayOut其实是不需要close的，这里close是为了防止一些代码检查工具提出警告
		byte[] message = out.toByteArray();
		return message;
	}

	/**
	 * 将内存数据块写入文件
	 * 
	 * @param file
	 * @param data
	 * @throws IOException
	 */
	public static void saveAsFile(File file, byte[] data) throws IOException {
		saveAsFile(file, false, data);
	}

	/**
	 * 将reader内容保存为文件
	 * 
	 * @param reader
	 * @param file
	 * @throws IOException
	 */
	public static void saveAsFile(File file, Charset charset, Reader... readers)
			throws IOException {
		BufferedWriter os = getWriter(file,
				charset == null ? null : charset.name(), false);
		try {
			for (Reader reader : readers) {
				copy(reader, os, true, false, new char[2048]);
			}
		} finally {
			closeQuietly(os);
		}
	}

	/**
	 * 将内存数据块写入文件
	 * 
	 * @param data
	 * @param file
	 * @throws IOException
	 */
	public static void saveAsFile(File file, boolean append, byte[] data)
			throws IOException {
		ensureParentFolder(file);
		OutputStream out = new FileOutputStream(file, append);
		try {
			out.write(data);
		} finally {
			if (out != null) {
				out.flush();
				out.close();
			}
		}
	}

	/**
	 * 获得文本文件写入流
	 * 
	 * @param target
	 * @param charSet
	 * @param append
	 * @return
	 * @throws IOException
	 */
	public static BufferedWriter getWriter(File target, String charSet,
			boolean append) {
		ensureParentFolder(target);
		try {
			OutputStream os = new FileOutputStream(target, append);
			if (charSet == null)
				charSet = Charset.defaultCharset().name();
			OutputStreamWriter osw = new OutputStreamWriter(os, charSet);
			return new BufferedWriter(osw);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

	}
}
